<?php
/**
 * RIA Css Optimizer
 * baset on Mynify CCS minimization 
 */

class CssOptimizer 
{
	/**
	 * 	The filename, which will be made local redirect	
	 *  
	 * @var string
	 */
	private $_cssRewriteFileName = 'style';
	
	/**
	 * The file extension of $_cssRewriteFileName
	 * 	
	 * @var string
	 */
	private $_cssRewriteFileExt = 'css';
	
	/**
	 * Temporary directory for cache file
	 *
	 * @var string
	 */
	private $_cssCacheDir = '../application/templates_c/cache';

	/**
	 * Cache file extension
	 *
	 * @var string
	 */
	private $_cssCacheExt = 'csscache';
	
	/**
	 * Directory for generating Cache JavaScript files
	 * 
	 * @var string
	 */
	private $_cssFilesDir = 'css/pack';

	/**
	 * Directory of web-server enviropment 'DOCUMENT_ROOT'
	 * 
	 * @var string
	 */
	private $_documentRootDir;
	
	/**
	 * Is use JavaScript minifier for result JavaScript Classes file 
	 * by JSMin http://code.google.com/p/jsmin-php/
	 *
	 * @var boolean
	 */
	private $_compressMode = true;

	/**
	 * Make Gzip copy of result JavaScript-file 
	 * for gzip-support browsers with '.jz.gz' extension
	 * 
	 * For use this feature in Apache make in $_jsFilesDir
	 * .htaccess file with text: 
	 *  
	 * RewriteEngine On
	 * RewriteRule (style)_(.*)\.css$ $1.css
	 *
	 * # Remove lines below if your apache don't support mod_headers
	 *
	 * <FilesMatch "\.css\.gz$">
	 * ForceType text/javascript
	 * Header set Content-Encoding: gzip
	 * </FilesMatch>
	 * <FilesMatch "style\.css$">
	 * RewriteEngine On
	 * RewriteCond %{HTTP:Accept-Encoding} gzip [OR]
	 * RewriteCond %{HTTP:TE} gzip
	 * RewriteCond %{REQUEST_FILENAME}.css -f
	 * RewriteRule (.*)\.css$ $1\.css.gz [L]
	 * ForceType text/javascript
	 * </FilesMatch>
	 * 
	 * @var boolean
	 */
	private $_gzipFileMode = true;
	
	
/**#@+
 * END JsClasses Configuration Section
 * There should be no need to touch anything below this line.
 */

	/**
	 * Array of classes cache data
	 *
	 * @var array
	 */
	private $_cssCacheData = array();
	
	/**
	 * Is time modify class checking
	 *
	 * @var array
	 */
	private $_checkClassModifyTime = true;

	/**
	 * Is cache rebuils required
	 *
	 * @var boolean
	 */
	private $_isCacheRebuildRequired = false;

	/**
	 * Cache JavaScript file
	 *
	 * @var string
	 */
	private $_cssFileName;

	/**
	 * Main CSS file
	 *
	 * @var string
	 */
	private $_mainCssFileName;
	
	/**
	 * Max modify time of target css files 
	 */
	private $_maxCssModifyTime;
	
/**#@-*/	
	
	
/*	public static function minify($cssFile) {
		$jsmin = new JSMin($js);
		return $jsmin->min();
	}	
*/	
	/**
	 * RIA Css Optimizer Constructor
	 * @param array $options array of minify options
	 *   cssRewriteFileName 
	 *   cssRewriteFileExt   
	 *   cssCacheDir        
	 *   cssFilesDir        
	 *   compressMode       
	 *   gzipFileMode       
	 */
	public function __construct($options = array()) {
		foreach ($options as $key => $value ) {
			$property = '_'.$key;
			$this->$property = $value;
		}
		if (isset($options['cssCacheDir'])) {$this->_cssCacheDir = $this->_spellDir($options['cssCacheDir']); }
		if (isset($options['cssFilesDir'])) {$this->_cssFilesDir = $this->_spellDir($options['cssFilesDir']); }
		
		if (isset($options['documentRootDir'])) {
			$this->_documentRootDir = $options['documentRootDir'];
		} else {
			$this->_documentRootDir = $_SERVER['DOCUMENT_ROOT'];
		}
		$this->_documentRootDir = $this->_spellDir($this->_documentRootDir);  
	}

	/**
	 * Optimize Css File
	 *
	 * @param array $files root-based files array of css-files. 
	 * @example 
	 *   $cssOptimizer = new CssOptimizer();
	 *   $cssOptimizer->minify($file)
	 */
	public function optimize($file) 
	{
		if (!file_exists($file)) {
			$this->trigger_error('File "'.$file.'" not found!',E_USER_ERROR);	
		}
		
		$this->_loadCssFilesCache($file);
		
		if (!$this->_isCacheRebuildRequired) {
			if ($this->_checkClassModifyTime) {
				 $this->_checkCss();
			}
		}
		if ($this->_isCacheRebuildRequired) {
			$this->_generateCssFile($file);
			$this->_generateCssFileName();
			$this->_generateCssCache();
			if ($this->_gzipFileMode) {
				$this->_gzipCssFile();
			}
		}
	}
	
	/**
	 * Get path for Virtual Cache Css file
	 * from root web-server directory
	 *
	 * @return string
	 */
	public function getCssFileName() {
		return $this->_cssFilesDir . '/' .$this->_cssFileName;
	}
	

    /**
     * trigger CssOptimizer error
     *
     * @param string $error_msg
     * @param integer $error_type
     */
    public function trigger_error($error_msg, $error_type = E_USER_WARNING) {
        trigger_error("CssOptimizer error: $error_msg", $error_type);
    }
	
	
	
	/**
	 * Get path for Real Cache Css file
	 * from root web-server directory
	 *
	 * @return string
	 */
	private function _getCssRealFileName() {
		return $this->_cssFilesDir . '/' .$this->_cssRewriteFileName . '.' . $this->_cssRewriteFileExt ;
	}

    /** 
     * Get Cache Css FileName
     * @return string
     */
    private function _getCssCacheFileName() {
    	return $this->_cssCacheDir . '/' . $this->_cssRewriteFileName . '.' . $this->_cssCacheExt;
    }
	
	
	/**
	 * Load Css Optimizer Cache
	 * Hint: cache-file format: full_filename__of_css_file:modification_time
	 */
	private function _loadCssFilesCache($file) {
		$cssCacheFile = $this->_getCssCacheFileName();
		if (is_file($cssCacheFile)) {
			$cssRecords = preg_split('/\n/', file_get_contents($cssCacheFile));
			$this->_cssFileName = array_shift($cssRecords);
			array_pop($cssRecords);
			foreach ($cssRecords as $cssRecord) {
				$cssRecordArray = preg_split('/\:/',$cssRecord);
				$this->_insertCssCacheDataRecord(
					$cssRecordArray[0], // $fileName
					$cssRecordArray[1]  // $lastModifyTime
				);
			}
		} else {
			$this->_isCacheRebuildRequired = true;
		}
		
		if ( (realpath($file) != $this->_mainCssFileName)
			 or (! file_exists($this->_getCssRealFileName())) ) {
				$this->_isCacheRebuildRequired = true;
		} 
		
	}
	
	/**
	 * Insert Css Info into _cssCacheData array  
	 *
	 * @param string $cssFileName
	 * @param path   $lastModifyTime
	 */
	private  function _insertCssCacheDataRecord($cssFileName,$lastModifyTime) {
		if (!count($this->_cssCacheData)) {
			$this->_mainCssFileName = $cssFileName;
		}
		$this->_cssCacheData[$cssFileName] = $lastModifyTime;
	}
	
	/**
//	 * Check is Classes is now in Css Cache
	 *
	 */
	private function _checkCss() {
		foreach ($this->_cssCacheData as $fileName => $lastModifyTime) {
			$lastModifyTimeNew = filemtime($fileName);
			if ($lastModifyTimeNew > $lastModifyTime) {
				$this->_isCacheRebuildRequired = true;
				return ;
			}
		}
	}
	
	/**
	 * Get Maximum lastModifyTime from Css array
	 */
	private function _getMaxCssModifyTime() {
		return max(array_values($this->_cssCacheData));
	}
	
	
	/**
	 * Generate CSS FileName
	 *
	 * @return string
	 */
	private function _generateCssFileName() {
		if (! $this->_maxCssModifyTime) {
			$this->_maxCssModifyTime = $this->_getMaxCssModifyTime();
		}
		$this->_isCacheRebuildRequired = false;
		$this->_cssFileName = $this->_cssRewriteFileName . '_' . $this->_maxCssModifyTime . '.' . $this->_cssRewriteFileExt;
	}
	
	/**
	 * Build All-In-One Optimized CSS-File
	 * 
	 */
	private function _generateCssFile($file) {
		$cssFilePath = $this->_getCssRealFileName();
		if (!$cssFilePathHandle = fopen($cssFilePath,'w')) {
			$this->trigger_error("File ".$cssFilePath." is not writable", E_USER_ERROR); 
		}

		require_once 'Minify/ImportProcessor.php';
		require_once 'Minify/CommentPreserver.php';
		require_once 'Minify/CSS.php';
		require_once 'Minify/CSS/UriRelative.php';
		require_once 'Minify/CSS/Compressor.php';
		
		$css = Minify_ImportProcessor::process($file);
		foreach (Minify_ImportProcessor::$filesIncluded as $fileName) {
			$this->_insertCssCacheDataRecord(
					$fileName,            // $fileName
					filemtime($fileName)  // $lastModifyTime
				);
		}

		if ($this->_compressMode) {
			$css = Minify_CSS::minify($css);
		}
		
		$css = Minify_CSS_UriRelative::rewrite($css,
			$this->_convertToRootRelative($this->_cssFilesDir)
		);
		
		fwrite($cssFilePathHandle, $css);
		if (flock($cssFilePathHandle, LOCK_EX)) { // Exclusive lock
		    flock($cssFilePathHandle, LOCK_UN); // Unlock
		}
		fclose($cssFilePathHandle);
	}
	
	/**
	 * Generate Css Optimizer cache-file
	 *
	 */
	private function _generateCssCache() {
		$cssCacheFile = $this->_getCssCacheFileName();
		if (!$cssCacheFileHandle = fopen($cssCacheFile,'w')) {
			$this->trigger_error("File ".$cssCacheFile." is not writable", E_USER_ERROR); 
		}
		
		if (flock($cssCacheFileHandle, LOCK_EX)) { // Exclusive lock
			fwrite($cssCacheFileHandle,$this->_cssFileName."\n");
			foreach (Minify_ImportProcessor::$filesIncluded as $fileName) {
				fwrite($cssCacheFileHandle, 
					$fileName . ':' . filemtime($fileName) . "\n"
				);
			}
		    flock($cssCacheFileHandle, LOCK_UN); // Unlock
		}
	}
	
	/**
	 * Make Gzip Css File
	 * with .css.gz extension
	 *
	 */
	private function _gzipCssFile() {
		$cssFilePath = $this->_getCssRealFileName();
		$cssFileGzipPath = $cssFilePath . '.gz';
		
		if (!$cssFilePathHandle = gzopen($cssFileGzipPath,'w')) {
			$this->trigger_error("File ".$cssFileGzipPath." is not writable", E_USER_ERROR); 
		}
		
		gzwrite($cssFilePathHandle, file_get_contents($cssFilePath));
		gzclose($cssFilePathHandle);
	}
	
	
	/**
	 * If the path has a slash at the end we remove it here
	 *
	 * @param string $dir
	 * @return string
	 */
	private function _spellDir($dir) 
	{ 
		return preg_replace('@/$@','',$dir); 
	}
	
	/**
	 * Convert relative path to rootRelative
	 */
	private function _convertToRootRelative($path) {
		if ('/' !== $path[0]) {
			$path = realpath($path);
			$path = substr($path, strlen($this->_documentRootDir.'/'));
		}
		return str_replace("\\", "/", $path); 
	}
}
?>